#include "downloadmanager.h"
#include "appglobal.h"
#include "appsetting.h"
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QFile>
#include <QFileInfo>
#include <QTime>
#include <QRegExp>
#include <algorithm>

YDDownloadManager::YDDownloadManager(QObject *parent) :
    QObject(parent) {
    mgr = new QNetworkAccessManager(this);
}

YDDownloadManager::~YDDownloadManager() {
}

void YDDownloadManager::startDownload(const QString& url, const QString& filePath, bool append) {
    url_map_t::iterator it = this->urlMap.find(url);

    if (it != this->urlMap.end()) {
        // 已经存在下载
        emit this->sigDuplicate(url);
        return;
    }
    
    if (!filePath.isEmpty()) {
        QFile* file = new QFile(filePath);

        if (file->exists()) {
            // 同名文件已经存在
            if (!append) {
                // 另建一个文件下载
                QString fixedFilePath = this->fixFilePath(file);

                if (fixedFilePath.isEmpty()) {
                    // 没有合适的名字
                    delete file;
                    emit this->sigErrorOccured(url);
                } else {
                    file->setFileName(fixedFilePath);
                    emit this->sigFileNameChanged(url, fixedFilePath);
                    if (!file->open(QIODevice::WriteOnly)) {
                        delete file;
                        emit this->sigErrorOccured(url);
                    } else {
                        this->startDownload(url, file);
                    }
                }
            } else {
                // 续传
                if (!file->open(QIODevice::Append)) {
                    delete file;
                    emit this->sigErrorOccured(url);
                } else {
                    this->startDownload(url, file);
                }
            }
        } else {
            // 文件不存在，新建文件下载
            if (!file->open(QIODevice::WriteOnly)) {
                delete file;
                emit this->sigErrorOccured(url);
            } else {
                this->startDownload(url, file);
            }
        }
    } else {
        this->startDownload(url);
    }
}

void YDDownloadManager::startDownload(const QString& url, QFile* file) {
    QUrl u(url);
    QNetworkRequest req(u);

    if (file) {
        // 目标文件已经存在
        QFileInfo info(*file);
        qint64 offset = info.size();
        QString range = QString("bytes ") + QString::number(offset) + QString("_");
        
        // 设置偏移
        req.setRawHeader("Range", range.toUtf8());
    }

    QNetworkReply* reply = this->mgr->get(req);
    void (QNetworkReply::*sig)(QNetworkReply::NetworkError) = &QNetworkReply::error;

    connect(reply, sig, this, &YDDownloadManager::on_reply_error);
    connect(reply, &QNetworkReply::downloadProgress, this, &YDDownloadManager::on_reply_download_progress);
    connect(reply, &QNetworkReply::finished, this, &YDDownloadManager::on_reply_finished);
    connect(reply, &QNetworkReply::metaDataChanged, this, &YDDownloadManager::on_reply_meta_data_changed);
    
    urlMap[url] = QPair<QNetworkReply*, QFile*>(reply, file);

    emit this->sigStarted(url);
}



void YDDownloadManager::stopDownload(const QString& url) {
    url_map_t::iterator it = this->urlMap.find(url);

    if (it == this->urlMap.end()) {
        // 不存在下载
        return;
    }

    QNetworkReply* reply = it->second.first;
    QFile* file = it->second.second;
    reply->close();
    reply->deleteLater();

    if (file) {
        file->flush();
        file->close();
        delete file;
    }

    emit this->sigStoped(url);
}

void YDDownloadManager::on_reply_error(int code) {
    QNetworkReply* reply = reinterpret_cast<QNetworkReply*>(this->sender());
    url_map_t::iterator it = this->findUrl(reply);
    
    if (it != this->urlMap.end()) {
        QString url = it->first;
        QFile* file = it->second.second;
        
        if ((code >= 301) && (code <= 399)) {
            // 重定向
            QString location = QString::fromUtf8(reply->header(QNetworkRequest::LocationHeader).toByteArray());

            if (!location.isEmpty()) {
                QUrl u(url);
                QNetworkRequest req(u);
                
                if (file && file->isOpen()) {
                    file->flush(); // 先将缓存的数据写进硬盘

                    QFileInfo info(*file);
                    qint64 offset = info.size();
                    QString range = QString("bytes ") + QString::number(offset) + QString("_");

                    // 设置偏移
                    req.setRawHeader("Range", range.toUtf8());
                }
                
                // 此时url 已经变了，但是我们不关心
                QNetworkReply* newReply = this->mgr->get(req);
                void (QNetworkReply::*sig)(QNetworkReply::NetworkError) = &QNetworkReply::error;

                connect(newReply, sig, this, &YDDownloadManager::on_reply_error);
                connect(newReply, &QNetworkReply::downloadProgress, this, &YDDownloadManager::on_reply_download_progress);
                connect(newReply, &QNetworkReply::finished, this, &YDDownloadManager::on_reply_finished);
                connect(newReply, &QNetworkReply::metaDataChanged, this, &YDDownloadManager::on_reply_meta_data_changed);
                
                it->second.first = newReply;
                reply->close();
                reply->deleteLater();

                return;
            }
        }
        
        file->close();
        delete file;
        reply->close();
        reply->deleteLater();
        this->urlMap.erase(it);
        emit this->sigErrorOccured(url, code);
    }
}

void YDDownloadManager::on_reply_download_progress(qint64 bytesReceived, qint64 bytesTotal) {
    QNetworkReply* reply = reinterpret_cast<QNetworkReply*>(this->sender());
    url_map_t::iterator it = this->findUrl(reply);
    
    if (it != this->urlMap.end()) {
        QString url = it->first;
        QFile* file = it->second.second;
        if (!file) {
            QString filename = this->getServerFileName(reply);
            QString downloadDir = g.settings->get("DOWNLOAD_DIR");
            QString filePath = downloadDir + filename;

            file = new QFile(filePath);

            if (file->exists()) {
                filePath = this->fixFilePath(file);
                if (!filePath.isEmpty()) {
                    file->setFileName(filePath);
                    if (!file->open(QIODevice::WriteOnly)) {
                        delete file;
                        reply->close();
                        reply->deleteLater();
                        this->urlMap.erase(it);
                        emit this->sigErrorOccured(url);
                    } else {
                        it->second.second = file;
                        emit this->sigFileNameChanged(url, filePath);
                    }
                } else {
                    delete file;
                    reply->close();
                    reply->deleteLater();
                    this->urlMap.erase(it);
                    emit this->sigErrorOccured(url);

                    return;
                }
            } else {
                file->setFileName(filePath);
                if (!file->open(QIODevice::WriteOnly)) {
                    delete file;
                    reply->close();
                    reply->deleteLater();
                    this->urlMap.erase(it);
                    emit this->sigErrorOccured(url);
                } else {
                    it->second.second = file;
                    emit this->sigFileNameChanged(url, filePath);
                }
            }
        }

        QByteArray data = reply->readAll();
        if (file->write(data) == -1) {
            file->close();
            delete file;
            reply->close();
            reply->deleteLater();
            this->urlMap.erase(it);
            emit this->sigErrorOccured(url);
        } else {
            file->flush();
            emit this->sigProgressChanged(url, bytesReceived, bytesTotal);
        }
    }
}

void YDDownloadManager::on_reply_finished() {
    QNetworkReply* reply = reinterpret_cast<QNetworkReply*>(this->sender());
    url_map_t::iterator it = this->findUrl(reply);

    if (it != this->urlMap.end()) {
        QString url = it->first;
        QFile* file = it->second.second;
        file->flush();
        file->close();
        delete file;
        reply->close();
        reply->deleteLater();

        emit this->sigFinished(url);
    }
}

void YDDownloadManager::on_reply_meta_data_changed() {
}

QString YDDownloadManager::fixFilePath(QFile* file) {
    Q_ASSERT(file);

    QString drive, dir, name, ext, fix;
    QString fixedFilePath;

    g.SplitPath(file->fileName(), drive, dir, name, ext);

    // 直到找到一个没有的新文件名
    for (int i = 0; i < INT_MAX; ++i) {
        fix.sprintf("_%d", i);

        fixedFilePath = g.MakePath(drive, dir, name + fix, ext);

        file->setFileName(fixedFilePath);
        if (!file->exists()) {
            break;
        }
    }

    return fixedFilePath;
}

YDDownloadManager::url_map_t::iterator YDDownloadManager::findUrl(QNetworkReply* reply) {
    return std::find_if(this->urlMap.begin(),
        this->urlMap.end(),
        [&](const std::pair<QString, QPair<QNetworkReply*, QFile*>>& obj)->bool{return(obj.second.first == reply);});
}

QString YDDownloadManager::getServerFileName(QNetworkReply* reply) {
    QString ret;
    QString dispos = QString::fromUtf8(reply->header(QNetworkRequest::ContentDispositionHeader).toByteArray());

    if (dispos.isEmpty()) {
        ret = QTime::currentTime().toString("hh_mm_ss_zzz") + ".ydh";
    } else {
        QRegExp reg("filename=\"([\\w\\.]+)\"", Qt::CaseInsensitive);

        if (reg.indexIn(dispos) < 0) {
            ret = QTime::currentTime().toString("hh_mm_ss_zzz") + ".ydh";
        } else {
            ret = reg.cap(1);
        }
    }

    return ret;
}
